package com.polidea.rxohosble2.sample.example4_characteristic;


import com.polidea.rxohosble2.RxBleConnection;
import com.polidea.rxohosble2.RxBleDevice;
import com.polidea.rxohosble2.internal.RxBleLog;
import com.polidea.rxohosble2.sample.ResourceTable;
import com.polidea.rxohosble2.sample.SampleApplication;
import com.polidea.rxohosble2.sample.util.HexString;
import com.polidea.rxohosble2.sample.DeviceAbility;
import com.polidea.rxohosble2.sample.util.ReplayingShare;
import com.polidea.rxohosble2.sample.util.Utils;
import io.reactivex.ohos.schedulers.OhosSchedulers;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.subjects.PublishSubject;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.app.Context;
import ohos.bluetooth.ble.GattCharacteristic;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import java.util.UUID;

public class CharacteristicOperationExampleAbility extends Ability {

    public static final String EXTRA_CHARACTERISTIC_UUID = "extra_uuid";

    Button connectButton;
    Text readOutputView;
    Text readHexOutputView;
    Text writeInput;
    Button readButton;
    Button writeButton;
    Button notifyButton;
    private UUID characteristicUuid;
    private PublishSubject<Boolean> disconnectTriggerSubject = PublishSubject.create();
    private Observable<RxBleConnection> connectionObservable;
    private RxBleDevice bleDevice;
    private final CompositeDisposable compositeDisposable = new CompositeDisposable();

    public static Intent startActivityIntent(Context context, String peripheralMacAddress, UUID characteristicUuid) {
        final Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
                .withDeviceId("")
                .withBundleName(context.getBundleName())
                .withAbilityName(CharacteristicOperationExampleAbility.class)
                .build();
        intent.setOperation(operation);
        intent.setParam(DeviceAbility.EXTRA_MAC_ADDRESS, peripheralMacAddress);
        intent.setParam(EXTRA_CHARACTERISTIC_UUID, characteristicUuid);
        return intent;
    }

    @Override
    protected void onStart(Intent intent) {
        super.onStart(intent);
        setPattern(ResourceTable.Pattern_base);
        setUIContent(ResourceTable.Layout_ability_example4);
        connectButton = (Button)findComponentById(ResourceTable.Id_connect);
        readOutputView = (Text) findComponentById(ResourceTable.Id_read_output);
        readHexOutputView = (Text) findComponentById(ResourceTable.Id_read_hex_output);
        writeInput = (Text) findComponentById(ResourceTable.Id_write_input);
        readButton = (Button)findComponentById(ResourceTable.Id_read);
        writeButton = (Button)findComponentById(ResourceTable.Id_write);
        notifyButton = (Button)findComponentById(ResourceTable.Id_notify);

        connectButton.setClickedListener(this::onConnectToggleClick);
        readButton.setClickedListener(this::onReadClick);
        writeButton.setClickedListener(this::onWriteClick);
        notifyButton.setClickedListener(this::onNotifyClick);

        Utils.setBackground(connectButton);
        Utils.setBackground(readButton);
        Utils.setBackground(writeButton);
        Utils.setBackground(notifyButton);

        String macAddress = getIntent().getStringParam(DeviceAbility.EXTRA_MAC_ADDRESS);
        characteristicUuid = (UUID) getIntent().getSerializableParam(EXTRA_CHARACTERISTIC_UUID);
        bleDevice = SampleApplication.getRxBleClient(this).getBleDevice(macAddress);
        connectionObservable = prepareConnectionObservable();
        //noinspection ConstantConditions
        ((Text) findComponentById(ResourceTable.Id_title)).setText(getString(ResourceTable.String_mac_address, macAddress));
    }

    private Observable<RxBleConnection> prepareConnectionObservable() {
        return bleDevice
                .establishConnection(false)
                .takeUntil(disconnectTriggerSubject)
                .compose(ReplayingShare.instance());
    }

    public void onConnectToggleClick(Component component) {
        if (isConnected()) {
            triggerDisconnect();
        } else {
            final Disposable connectionDisposable = connectionObservable
                    .flatMapSingle(RxBleConnection::discoverServices)
                    .flatMapSingle(rxBleDeviceServices -> rxBleDeviceServices.getCharacteristic(characteristicUuid))
                    .observeOn(OhosSchedulers.mainThread())
                    .doOnSubscribe(disposable -> connectButton.setText(ResourceTable.String_connecting))
                    .subscribe(
                            characteristic -> {
                                updateUI(characteristic);
                                RxBleLog.i("Hey, connection has been established!");
                            },
                            this::onConnectionFailure,
                            this::onConnectionFinished
                    );

            compositeDisposable.add(connectionDisposable);
        }
    }

    public void onReadClick(Component component) {
        if (isConnected()) {
            final Disposable disposable = connectionObservable
                    .firstOrError()
                    .flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(characteristicUuid))
                    .observeOn(OhosSchedulers.mainThread())
                    .subscribe(bytes -> {
                        readOutputView.setText(new String(bytes));
                        readHexOutputView.setText(HexString.bytesToHex(bytes));
                        writeInput.setText(HexString.bytesToHex(bytes));
                    }, this::onReadFailure);

            compositeDisposable.add(disposable);
        }
    }

    public void onWriteClick(Component component) {

        RxBleLog.i("onWriteClick");
        if (isConnected()) {
            RxBleLog.i("onWriteClick do");
            final Disposable disposable = connectionObservable
                    .firstOrError()
                    .flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(characteristicUuid, getInputBytes()))
                    .observeOn(OhosSchedulers.mainThread())
                    .subscribe(
                            bytes -> onWriteSuccess(),
                            this::onWriteFailure
                    );

            compositeDisposable.add(disposable);
        }
    }

    public void onNotifyClick(Component component) {

        if (isConnected()) {
            final Disposable disposable = connectionObservable
                    .flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristicUuid))
                    .doOnNext(notificationObservable -> runOnUiThread(this::notificationHasBeenSetUp))
                    .flatMap(notificationObservable -> notificationObservable)
                    .observeOn(OhosSchedulers.mainThread())
                    .subscribe(this::onNotificationReceived, this::onNotificationSetupFailure);

            compositeDisposable.add(disposable);
        }
    }

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread().getId() != EventRunner.getMainEventRunner().getThreadId()) {
            EventHandler.current().postSyncTask(action);
        } else {
            action.run();
        }
    }

    private boolean isConnected() {
        return bleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.CONNECTED;
    }

    private void onConnectionFailure(Throwable throwable) {
        //noinspection ConstantConditions
        Utils.showToast(this, "Connection error: "+ throwable);
        updateUI(null);
    }

    private void onConnectionFinished() {
        updateUI(null);
    }

    private void onReadFailure(Throwable throwable) {
        //noinspection ConstantConditions
        Utils.showToast(this, "Read error: "+ throwable);
    }

    public boolean isWriteFlage() {
        return writeFlage;
    }

    boolean writeFlage=false;

    private void onWriteSuccess() {
        //noinspection ConstantConditions
        writeFlage=true;
        Utils.showToast(this, "Write success ");
    }

    private void onWriteFailure(Throwable throwable) {
        //noinspection ConstantConditions
        Utils.showToast(this, "Write error: "+throwable);
    }

    private void onNotificationReceived(byte[] bytes) {
        //noinspection ConstantConditions
        Utils.showToast(this, "Change: "+HexString.bytesToHex(bytes));
    }

    private void onNotificationSetupFailure(Throwable throwable) {
        //noinspection ConstantConditions
        Utils.showToast(this, "Notifications error:"+throwable);
    }

    private void notificationHasBeenSetUp() {
        //noinspection ConstantConditions
        Utils.showToast(this, "Notifications has been set up");
    }

    private void triggerDisconnect() {
        disconnectTriggerSubject.onNext(true);
    }

    /**
     * This method updates the UI to a proper state.
     *
     * @param characteristic a nullable {@link GattCharacteristic}. If it is null then UI is assuming a disconnected state.
     */
    private void updateUI(GattCharacteristic characteristic) {
        connectButton.setText(characteristic != null ? ResourceTable.String_disconnect : ResourceTable.String_connect);
        readButton.setEnabled(hasProperty(characteristic, GattCharacteristic.PROPERTY_READ));
        writeButton.setEnabled(hasProperty(characteristic, GattCharacteristic.PROPERTY_WRITE));
        notifyButton.setEnabled(hasProperty(characteristic, GattCharacteristic.PROPERTY_WRITE_NO_RESPONSE));//TODO:ERRO 缺少通知属性PROPERTY_NOTIFY，暂时设置为无响应PROPERTY_WRITE_NO_RESPONSE
    }

    private boolean hasProperty(GattCharacteristic characteristic, int property) {
        return characteristic != null && (characteristic.getProperties() & property) > 0;
    }

    private byte[] getInputBytes() {
        return HexString.hexToBytes(writeInput.getText().toString());
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        compositeDisposable.clear();
    }
}
